from dataclasses import dataclass
import numpy as np

from .move import Direction

def isTwoRangeCollide(x : int, y : int, a : int, b : int) -> bool:
    """return if there is cross between [x, y) and [a, b)"""
    return max(x, a) < min(y, b)


@dataclass
class CoordIndex():
    LEFT_UP : int
    LEFT_DOWN : int
    RIGHT_UP : int
    RIGHT_DOWN : int

COORD_INDEX = CoordIndex(0, 1, 2, 3)
CAR_WIDTH = 1


class Car():
    """
    Takes the dict like:
    {
        "length": 2,
        "position": {
            "x": 0,
            "y": 2
        },
        "allow_direction": "x"
    }
    """

    LENGTH_KEY_NAME = 'length'
    POSITION_KEY_NAME = 'position'
    ALLOW_DIRECTION_KEY_NAME = 'allow_direction'
    X_KEY_NAME = 'x'
    Y_KEY_NAME = 'y'

    def __init__(self, car_dict: dict) -> None:
        length : int = car_dict[self.LENGTH_KEY_NAME]
        position : dict[str, int] = car_dict[self.POSITION_KEY_NAME]
        allow_direction : str = car_dict[self.ALLOW_DIRECTION_KEY_NAME]

        start_point = np.array([position[self.X_KEY_NAME], position[self.Y_KEY_NAME]])

        self.coords = np.zeros((4, 2), dtype=np.int8)
        self.allow_direction = {'x': Direction.X, 'y': Direction.Y}[allow_direction]
        self.is_main_car : bool = False

        if allow_direction == 'x':
            self.coords[COORD_INDEX.LEFT_UP] =      start_point
            self.coords[COORD_INDEX.RIGHT_UP] =     start_point + np.array([length, 0])
            self.coords[COORD_INDEX.RIGHT_DOWN] =   start_point + np.array([length, CAR_WIDTH])
            self.coords[COORD_INDEX.LEFT_DOWN] =    start_point + np.array([0, CAR_WIDTH])
        elif allow_direction == 'y':
            self.coords[COORD_INDEX.LEFT_UP] =      start_point
            self.coords[COORD_INDEX.RIGHT_UP] =     start_point + np.array([CAR_WIDTH, 0])
            self.coords[COORD_INDEX.RIGHT_DOWN] =   start_point + np.array([CAR_WIDTH, length])
            self.coords[COORD_INDEX.LEFT_DOWN] =    start_point + np.array([0, length])


    def isHitTheWall(self) -> bool:
        if not self.is_main_car:
            return np.any(self.coords < 0) or np.any(self.coords > 6)
        else:
            return np.any(self.coords < 0) or np.any(self.coords[:, 1] > 6)


    def isCollideWith(self, other) -> bool:
        collide_list = [
            isTwoRangeCollide(
                self.coords[COORD_INDEX.LEFT_UP][index], self.coords[COORD_INDEX.RIGHT_DOWN][index],
                other.coords[COORD_INDEX.LEFT_UP][index], other.coords[COORD_INDEX.RIGHT_DOWN][index]
            ) for index in [0, 1]
        ]

        return all(collide_list)


    def isCollideWithOtherCars(self, other_cars : list) -> bool:
        for other_car in other_cars:
            if self.isCollideWith(other_car):
                return True

        return False


    def Move(self, diff : np.ndarray, other_cars : list):
        # TODO: Should use better way to check if will hit other cars in the whole moving.

        steps = abs(np.sum(diff))
        single_diff = np.sign(diff)
        for _ in range(steps):
            self.coords += single_diff

            if self.isCollideWithOtherCars(other_cars):
                raise Exception("Collide with other cars.")

        if self.isHitTheWall():
            raise Exception("Hit the walls.")


    def IsWin(self) -> bool:
        if not self.is_main_car:
            return False

        return self.coords[COORD_INDEX.LEFT_UP][0] in {6, 7}
